using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace NalogGarLoader.Data;

public class GarImportParameters
{
    [JsonInclude, JsonConverter(typeof(JsonStringEnumConverter))]
    public ImportMode ImportMode = ImportMode.Manual;

    [JsonInclude, JsonConverter(typeof(JsonStringEnumConverter))]
    public InsertMode InsertMode = InsertMode.Update;

    [JsonInclude, JsonConverter(typeof(JsonStringEnumConverter))]
    public CleanupMode CleanupMode = CleanupMode.None;

    [JsonInclude]
    public int BulkInsertBufferSize = 1000;

    [JsonInclude]
    public string TmpDir = Environment.CurrentDirectory;

    [JsonInclude]
    public string? Source;

    [JsonInclude]
    public string? ProviderName;

    [JsonInclude]
    public string? ConnectionString;

    [JsonInclude]
    public HashSet<string>? IncludeGarTypes;

    [JsonInclude]
    public HashSet<string>? ExcludeGarTypes;

    [JsonInclude]
    public HashSet<string>? IncludeStates;

    [JsonInclude]
    public HashSet<string>? ExcludeStates;    

    [JsonInclude]
    public DateOnly? LastImportGarVer;

    [JsonInclude]
    public int MaxDOP = 8;

    [JsonInclude]
    public bool InitDbOnly = false;

    public void InitFromArgs(string[] args)
    {
        for (int i = 0; i < args.Length; i++)
        {
            switch (args[i].ToLower())
            {
                case "-importmode":
                    try
                    {
                        this.ImportMode = (ImportMode)Enum.Parse(typeof(ImportMode), args[++i], true);
                    }
                    catch (Exception ex)
                    {
                        throw new AggregateException("Некорретный аргумент параметра -importMode", ex);
                    }
                    break;

                case "-insertmode":
                    try
                    {
                        this.InsertMode = (InsertMode)Enum.Parse(typeof(InsertMode), args[++i], true);
                    }
                    catch (Exception ex)
                    {
                        throw new AggregateException("Некорретный аргумент параметра -insertMode", ex);
                    }
                    break;

                case "-cleanupmode":
                    try
                    {
                        this.CleanupMode = (CleanupMode)Enum.Parse(typeof(CleanupMode), args[++i], true);
                    }
                    catch (Exception ex)
                    {
                        throw new AggregateException("Некорретный аргумент параметра -cleanupMode", ex);
                    }
                    break;

                case "-bulkinsertbuffersize":
                    try
                    {
                        this.BulkInsertBufferSize = int.Parse(args[++i]);
                    }
                    catch (Exception ex)
                    {
                        throw new AggregateException("Не корректно задан аргумент параметра BulkInsertBufferSize", ex);
                    }

                    break;

                case "-maxdop":
                    try
                    {
                        this.MaxDOP = int.Parse(args[++i]);
                    }
                    catch (Exception ex)
                    {
                        throw new AggregateException("Не корректно задан аргумент параметра MaxDOP", ex);
                    }

                    break;                    

                case "-tmpdir":
                    this.TmpDir = args[++i];
                    break;

                case "-source":
                    this.Source = args[++i];
                    break;

                case "-includegartypes":
                    this.IncludeGarTypes = args[++i].Replace(" ", "").Split(",").ToHashSet();
                    break;

                case "-excludegartypes":
                    this.ExcludeGarTypes = args[++i].Replace(" ", "").Split(",").ToHashSet();
                    break;

                case "-includestates":
                    this.IncludeStates = args[++i].Replace(" ", "").Split(",").ToHashSet();
                    break;

                case "-excludestates":
                    this.ExcludeStates = args[++i].Replace(" ", "").Split(",").ToHashSet();
                    break;                    

                case "-providername":
                    this.ProviderName = args[++i];
                    break;

                case "-connectionstring":
                    this.ConnectionString = args[++i];
                    break;

                case "-lastimportgarver":
                    try
                    {
                        this.LastImportGarVer = DateOnly.ParseExact(args[++i], "yyyyMMdd");
                    }
                    catch (Exception ex)
                    {
                        throw new AggregateException("Не корректно задан аргумент параметра LastImportGarVer: ожидаемый формат даты yyyyMMdd", ex);
                    }
                    break;

                case "-initdbonly":
                    this.InitDbOnly = true;
                    break;

                default:
                    throw new ArgumentException($"Неизвестный параметр: {args[i]}. Введите -h для получения списка допустимых параметров.");
            }
        }
    }

    public void InitFromJson(string jsonparsedParametersFile)
    {
        if (string.IsNullOrEmpty(jsonparsedParametersFile))
            throw new ArgumentNullException(nameof(jsonparsedParametersFile));

        if (!File.Exists(jsonparsedParametersFile))
            throw new FileNotFoundException(jsonparsedParametersFile);

        GarImportParameters? parsedParameters = null;

        using (var jsonStream = File.OpenRead(jsonparsedParametersFile))
        {
            JsonSerializerOptions options = new JsonSerializerOptions()
            {
                PropertyNameCaseInsensitive = true            
            };
            
            parsedParameters = JsonSerializer.Deserialize<GarImportParameters>(jsonStream, options);
        }

        if (parsedParameters is null)
            return;

        this.ImportMode = parsedParameters.ImportMode;
        this.InsertMode = parsedParameters.InsertMode;
        this.CleanupMode = parsedParameters.CleanupMode;
        this.BulkInsertBufferSize = parsedParameters.BulkInsertBufferSize;
        this.TmpDir = parsedParameters.TmpDir ?? this.TmpDir;
        this.Source = parsedParameters.Source ?? this.Source;
        this.ProviderName = parsedParameters.ProviderName;
        this.ConnectionString = parsedParameters.ConnectionString ?? this.ConnectionString;
        this.IncludeGarTypes = parsedParameters.IncludeGarTypes?.Count > 0 ? parsedParameters.IncludeGarTypes : null;
        this.ExcludeGarTypes = parsedParameters.ExcludeGarTypes?.Count > 0 ? parsedParameters.ExcludeGarTypes : null;    
        this.IncludeStates = parsedParameters.IncludeStates?.Count > 0 ? parsedParameters.IncludeStates : null;
        this.ExcludeStates = parsedParameters.ExcludeStates?.Count > 0 ? parsedParameters.ExcludeStates : null;            
        this.LastImportGarVer = parsedParameters.LastImportGarVer ?? this.LastImportGarVer;
        this.MaxDOP = parsedParameters.MaxDOP;
        this.InitDbOnly = parsedParameters.InitDbOnly;
    }

    public void Validate()
    {
        if (this.IncludeGarTypes is not null)
        {
            var incorrectTypes = this.IncludeGarTypes.Except(GarTypes.GarTypeSet).ToList();

            if (incorrectTypes.Count > 0)
                throw new ArgumentException($"Указаны некорректные имена типов ГАР для импорта: {string.Join(", ", incorrectTypes)}");
        }

        if (this.ImportMode == ImportMode.Manual)
        {
            if (string.IsNullOrEmpty(this.Source) && ! this.InitDbOnly)
                throw new ArgumentNullException(nameof(Source), "При ручном режиме импорта указание параметра Source обязательно");
        }

        if (string.IsNullOrEmpty(this.TmpDir))
            throw new ArgumentNullException(nameof(TmpDir), "Не указана директория для временного хранения файлов в параметре TmpDir");

        if (!Directory.Exists(this.TmpDir))
            throw new DirectoryNotFoundException($"Директория TmpDir: \"{this.TmpDir}\" не найдена");

        if (string.IsNullOrEmpty(this.ConnectionString))
            throw new ArgumentNullException(nameof(ConnectionString), "Строка подключения не задана в параметре ConnectionString");

        if (string.IsNullOrEmpty(this.ProviderName))
            throw new ArgumentNullException(nameof(ProviderName), "Не указан тип БД в параметре ProviderName");

        if (this.BulkInsertBufferSize <= 0)
            throw new ArgumentOutOfRangeException(nameof(BulkInsertBufferSize), "Параметр BulkInsertBufferSize должен быть больше 0");

        if (this.MaxDOP <= 0)
            throw new ArgumentOutOfRangeException(nameof(MaxDOP), "Параметр MaxDOP должен быть больше 0");            
    }

    public static void PrintConsoleHelp()
    {
        Console.WriteLine("Usage:");
        Console.WriteLine("-providerName <str value>");
        Console.WriteLine("\tИмя провайдера. Со списком доступных провайдеров можно ознакомиться здесь: https://linq2db.github.io/articles/general/databases.html");
        Console.WriteLine("\tДля работы должен быть установлен соответсвующий ADO.NET провайдер.");
        Console.WriteLine("");

        Console.WriteLine("-connectionString <str value>");
        Console.WriteLine("\tСтрока подключения к БД.");
        Console.WriteLine("");
        
        Console.WriteLine("[-importMode <import_mode_enum_value>]");
        Console.WriteLine("\tРежим импорта. Доступные значения:");
        Console.WriteLine("\t\tmanual\t- (по умолчанию) импортирует в БД заранее скачанный локально ГАР, указанный в параметре -source.");
        Console.WriteLine("\t\tfull\t- скачать последнюю версию ГАР, и импортировать ее.");
        Console.WriteLine("\t\tupdate\t- скачать все обновления ГАР с момента последней загрузки (информация о последней загрузки берется из таблицы CurrentVersion).");
        Console.WriteLine("");

        Console.WriteLine("[-insertMode <insertMode_mode_enum_value>]");
        Console.WriteLine("\tСпособ вставки в записей в БД. Доступные значения:");
        Console.WriteLine("\t\tupdate\t- (по умолчанию) для вставки будет использоваться merge (если поддерживается БД) или select + insert.");
        Console.WriteLine("\t\tinsert\t- только вставка записей. Будет использоватсья наиболее эффективный способ вставки, поддерживаемый провайдером (напр: bulk insert, multiple rows).");
        Console.WriteLine("\t\t\t\tПодробнее можно ознакомиться здесь: https://linq2db.github.io/articles/sql/Bulk-Copy.html");
        Console.WriteLine("");

        Console.WriteLine("[-cleanupMode <cleanupMode_mode_enum_value>]");
        Console.WriteLine("\tОчистка таблиц перед импортом. Доступные методы очистки:");
        Console.WriteLine("\t\tnone\t- (по умолчанию) Не выполнять очистку.");
        Console.WriteLine("\t\tdelete\t- Будет использоваться запроос delete from table.");
        Console.WriteLine("\t\ttruncate\t- Будет использоваться запроос truncate table. Убедитесь, что БД поддерживает конструкцию.");
        Console.WriteLine("\t\tdropTable\t- Таблица будет удалена, и создана заново.");
        Console.WriteLine("");        

        Console.WriteLine("[-source <str value>]");
        Console.WriteLine("\tДиректория с распакованным ГАР, или путь к скаченному архиву ГАР. Обязательно для ImportMode = Manual. Для остальных игнорируется.");
        Console.WriteLine("");

        Console.WriteLine("[-tmpDir <str value>]");
        Console.WriteLine("\tДиректория для временного хранения скаченных архивов в режимах импорта ImportMode = Full, Update. Для Manual игнорируется.");
        Console.WriteLine("");

        Console.WriteLine("[-bulkInsertBufferSize <int value>");
        Console.WriteLine("\tРазмер буфера для режима InsertMode.Insert. По умолчанию - 1000.");
        Console.WriteLine("");        

        Console.WriteLine("[-maxdop <int value>");
        Console.WriteLine("\tМаксимально кол-во потоков для импорта xml фалов. По умолчанию - 8.");
        Console.WriteLine("");                

        Console.WriteLine("[-lastImportGarVer <date yyyyMMdd value>]");
        Console.WriteLine("\tЗадает дату последней импортированной версии ГАР.");
        Console.WriteLine("\t\tВ режиме Manual игнорируется.");
        Console.WriteLine("\t\tВ режиме импорта Full будет скачана ближайшая полная выгрузка после этой даты.");
        Console.WriteLine("\t\tВ режиме импорта Update будут скачаны все инкреметные выгрузки от заданной до текущей даты (иначе будет взята дата из БД)");
        Console.WriteLine("");        
        
        Console.WriteLine("[-includeGarTypes <GarType(1),GarType(2),...,GarType(N)]");
        Console.WriteLine("\tИмпортировать только указанные типы.");
        Console.WriteLine("");

        Console.WriteLine("[-excludeGarTypes <GarType(1),GarType(2),...,GarType(N)]");
        Console.WriteLine("\tИгнорировать при импорте указанные типы.");
        Console.WriteLine("");

        Console.WriteLine("[-excludeStates <StateId(1),StateId(2),...,StateId(N)]");
        Console.WriteLine("\tИмпортировать только указанные регионы.");
        Console.WriteLine("");

        Console.WriteLine("[-excludeGarTypes <StateId(1),StateId(2),...,StateId(N)]");
        Console.WriteLine("\tИгнорировать при импорте указанные регионы.");
        Console.WriteLine("");

        Console.WriteLine("[-initDbOnly");
        Console.WriteLine("\tТолько инициализировать БД");
        Console.WriteLine("");           
    }
}